package is.glif.fenius.webui.topo;



import is.glif.fenius.webui.common.WebUIException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;

public class DjikstraPathProvider implements PathProvider {
    private static Logger log = Logger.getLogger(DjikstraPathProvider.class);
    private static DjikstraPathProvider instance;
    public static DjikstraPathProvider getInstance() {
        if (instance == null) {
            instance = new DjikstraPathProvider();
        }
        return instance;
    }
    private DjikstraPathProvider() {
        
    }

    public JSONArray getPath(String src, String dst) throws WebUIException {
        log.debug("getPath.start");
        log.debug("src: "+src);
        log.debug("dst: "+dst);
        
        Vertex srcV = null;
        Vertex dstV = null;
        
        Graph<Vertex, Edge> g = new UndirectedSparseGraph<Vertex, Edge>();
        
        FileEdgeProvider ep = FileEdgeProvider.getInstance();
        FileNetworkProvider np = FileNetworkProvider.getInstance();
        try {
            JSONArray networks = np.getNetworks("");
            
            for (int i = 0; i < networks.length(); i++) {
                JSONObject networkObj = networks.getJSONObject(i);
                String netId = networkObj.getString("id");
                log.debug("network: "+netId);
                // add a vertex for the network itself
                Vertex netVertex = new Vertex();
                netVertex.setId(netId);
                netVertex.setNetId(netId);
                netVertex.setUrn(netId);
                g.addVertex(netVertex);
                
                
                JSONArray edges = ep.getEdges(netId, "", "");
                for (int j = 0; j < edges.length(); j++) {
                    JSONObject edgeObj = edges.getJSONObject(j);
                    
                    String borderUrn = edgeObj.getString("urn");
                    
                    // make the endpoint urn a unique string
                    String uniqueUrn = netId+"::"+borderUrn;
    
                    // add a vertex for the endpoint 
                    Vertex endpointVertex = new Vertex();
                    endpointVertex.setId(uniqueUrn);
                    endpointVertex.setNetId(netId);
                    endpointVertex.setUrn(borderUrn);
                    g.addVertex(endpointVertex);
                    
                    if (src.equals(borderUrn)) {
                        srcV = endpointVertex;
                    } else if (dst.equals(borderUrn)) {
                        dstV = endpointVertex;
                    }
    
                    
                    // the name of the edge from the network vertex to this border urn
                    String netToEndpointId = netId+ "---"+uniqueUrn;
                    Edge netToEndpoint = new Edge();
                    netToEndpoint.setId(netToEndpointId);
                    netToEndpoint.setVertexA(endpointVertex);
                    netToEndpoint.setVertexZ(netVertex);
                    
                    g.addEdge(netToEndpoint, endpointVertex, netVertex);
                    
                    if (edgeObj.has("adjacencies")) {
                        JSONArray adjacencies = edgeObj.getJSONArray("adjacencies");
                        for (int k = 0; k < adjacencies.length(); k++) {
                            JSONObject adjData = adjacencies.getJSONObject(k);
                            String adjType = adjData.getString("type");

                            if (adjType.equals("network")) {
                                String adjNetId  = adjData.getString("peer");
                                String adjUrn = adjData.getString("remoteUrn");
                                Integer weight = 1;
                                if (adjData.has("weight")) {
                                    weight = adjData.getInt("weight");
                                }

                                
                                if (borderUrn.equals(src) || borderUrn.equals(dst)) {
                                    log.debug("skipping adjacency for terminating edge "+borderUrn);
                                } else if (adjUrn.equals(src) || adjUrn.equals(dst) ) {
                                    log.debug("skipping adjacency for terminating edge "+adjUrn);
                                } else {
                                    String uniqueAdjUrn = adjNetId+"::"+adjUrn;
                                    // add a vertex for peer network
                                    Vertex adjNetVertex = new Vertex();
                                    adjNetVertex.setId(adjNetId);
                                    adjNetVertex.setNetId(adjNetId);
                                    adjNetVertex.setUrn(adjNetId);
                                    g.addVertex(adjNetVertex);
                                    
                                    // add a vertex for adjacent endpoint
                                    Vertex adjEndpointVertex = new Vertex();
                                    adjEndpointVertex.setId(uniqueAdjUrn);
                                    adjEndpointVertex.setNetId(adjNetId);
                                    adjEndpointVertex.setUrn(adjUrn);
                                    g.addVertex(adjEndpointVertex);
                                    
                                    // the name of the edge from the adjacent network vertex to its border urn
                                    String adjNetToEndpointId = adjNetId+ "---"+uniqueAdjUrn;
                                    Edge adjNetToEndpoint = new Edge();
                                    adjNetToEndpoint.setId(adjNetToEndpointId);
                                    adjNetToEndpoint.setVertexA(adjNetVertex);
                                    adjNetToEndpoint.setVertexZ(adjEndpointVertex);
                                    g.addEdge(adjNetToEndpoint, adjEndpointVertex, adjNetVertex);
                                    
                                    // the name of the edge from our border urn to the neighbor border urn
                                    String borderToBorderId = uniqueAdjUrn+"---"+uniqueUrn;
                                    Edge borderToBorderE = new Edge();
                                    borderToBorderE.setId(borderToBorderId);
                                    borderToBorderE.setVertexA(adjEndpointVertex);
                                    borderToBorderE.setVertexZ(endpointVertex);
                                    borderToBorderE.setWeight(weight.doubleValue());
                                    
                                    g.addEdge(borderToBorderE, adjEndpointVertex, endpointVertex);
                                    log.debug(uniqueAdjUrn+"---"+uniqueUrn+"  weight: "+weight);
                                }
                            }
                        }
                    }
                }
            }
        } catch (JSONException e) {
            log.error(e);
            throw new WebUIException(e.getMessage());
        }

        // log.debug(g.toString());

        ArrayList<String> netSeq = new ArrayList<String>();
        
        
        Transformer<Edge, Double> wtTransformer;
        wtTransformer = new Transformer<Edge, Double>() { 
            public Double transform(Edge edge) { 
                return edge.getWeight(); 
            } 
        }; 

        HashMap<String, ArrayList<String>> netHops = new HashMap<String, ArrayList<String>>();
        DijkstraShortestPath<Vertex, Edge> p = new DijkstraShortestPath<Vertex, Edge>(g, wtTransformer);
        
        
        List<Edge> hops = p.getPath(srcV, dstV);
        for (Edge hop : hops) {
            log.debug("hop: "+hop.getId());
            String aNetId = hop.getVertexA().getNetId();
            String zNetId = hop.getVertexZ().getNetId();
            if (aNetId.equals(zNetId)) {
                if (!netSeq.contains(aNetId)) {
                    log.debug("adding "+aNetId+" to netSeq");
                    netSeq.add(aNetId);
                }
                if (netHops.get(aNetId) == null) {
                    netHops.put(aNetId, new ArrayList<String>());
                }
                
                String aUrn = hop.getVertexA().getUrn();
                String zUrn = hop.getVertexZ().getUrn();
                
                if (aUrn.equals(aNetId)) {
                    log.debug("adding "+zUrn+" to hops for "+aNetId);
                    netHops.get(aNetId).add(zUrn);
                } else {
                    log.debug("adding "+aUrn+" to hops for "+zNetId);
                    netHops.get(aNetId).add(aUrn);
                }
            }
        }
        JSONArray path = new JSONArray();
        String pathStr = "";
        try {
            for (String netId : netSeq) {
                for (String urn : netHops.get(netId)) {
                    JSONObject hopObj = new JSONObject();
                    hopObj.put("name", netId);
                    hopObj.put("network", netId);
                    hopObj.put("urn", urn);
                    pathStr += "urn: "+netId+"::"+urn+"\n";
                    path.put(hopObj);
                }
            }
        } catch (JSONException e) {
            log.error(e);
            throw new WebUIException(e.getMessage());
        }
        log.info(pathStr);
        log.debug("getPath.end");
        
        return path;
    }
    
    private class Vertex {
        private String id;

        private String urn;
        private String netId;
        @SuppressWarnings("rawtypes")
        public boolean equals(Object o) {
            if (this == o) { return true; }
            Class thisClass = getClass();
            if (o == null || thisClass != o.getClass()) {
                return false;
            }
            Vertex other = (Vertex) o;
            if (other.getId() == null) {
                return false;
            } else {
                return (other.getId().equals(id));
            }
        }
        public int hashCode() {
            return new HashCodeBuilder()
                .append(getId())
                .toHashCode();
        }

        public String toString() {
            return id;
        }

        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getUrn() {
            return urn;
        }
        public void setUrn(String urn) {
            this.urn = urn;
        }
        public String getNetId() {
            return netId;
        }
        public void setNetId(String netId) {
            this.netId = netId;
        }
    }
    
    private class Edge {
        private String id;
        private Vertex vertexA;
        private Vertex vertexZ;
        private Double weight = 1.0;
        
        public Double getWeight() {
            return weight;
        }
        public void setWeight(Double weight) {
            this.weight = weight;
        }
        public String toString() {
            return id;
        }
        @SuppressWarnings("rawtypes")
        public boolean equals(Object o) {
            if (this == o) { return true; }
            Class thisClass = getClass();
            if (o == null || thisClass != o.getClass()) {
                return false;
            }
            Edge other = (Edge) o;
            if (other.getId() == null) {
                return false;
            } else {
                return (other.getId().equals(id));
            }
        }
        public int hashCode() {
            return new HashCodeBuilder()
                .append(getId())
                .toHashCode();
        }

        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public void setVertexA(Vertex vertexA) {
            this.vertexA = vertexA;
        }
        public Vertex getVertexA() {
            return vertexA;
        }
        public void setVertexZ(Vertex vertexZ) {
            this.vertexZ = vertexZ;
        }
        public Vertex getVertexZ() {
            return vertexZ;
        }
        
    }


}
